import type { CreateAxiosOptions } from './axiosTransform'
import type { AxiosRequestConfig, AxiosInstance, AxiosResponse, AxiosError } from 'axios'
import axios from 'axios'
import qs from 'qs'
import { AxiosCanceler } from './axiosCancel'
import { ContentTypeEnum, RequestEnum } from '@/constants'
import { cloneDeep, isFunction } from '@/utils'
export * from './axiosTransform'

type RequestOptions = AxiosType.RequestOptions
type RequestResult = AxiosType.RequestResult
/**
 * @description: axios module
 */
export class IAxios {
	private axiosInstance: AxiosInstance
	private options: CreateAxiosOptions

	constructor(options: CreateAxiosOptions) {
		this.options = options
		this.axiosInstance = axios.create(options)
		this.setupInterceptors()
	}

	/**
	 * @description: Create axios instance
	 */
	private createAxios(config: CreateAxiosOptions): void {
		this.axiosInstance = axios.create(config)
	}

	private getTransform() {
		const { transform } = this.options
		return transform
	}

	getAxios(): AxiosInstance {
		return this.axiosInstance
	}

	/**
	 * @description: Reconfigure axios
	 */
	configAxios(options: CreateAxiosOptions) {
		if (!this.axiosInstance) {
			return
		}
		const opt: CreateAxiosOptions = Object.assign({}, this.options, options)
		this.createAxios(opt)
		this.options = opt
		this.setupInterceptors()
	}

	/**
	 * @description: Set general header
	 */
	setHeader(headers: any): void {
		if (!this.axiosInstance) {
			return
		}
		Object.assign(this.axiosInstance.defaults.headers, headers)
	}

	/**
	 * @description: Interceptor configuration
	 */
	private setupInterceptors() {
		const transform = this.getTransform()
		if (!transform) {
			return
		}
		const { requestInterceptors, requestInterceptorsCatch, responseInterceptors, responseInterceptorsCatch } = transform

		const axiosCanceler = new AxiosCanceler()

		// Request interceptor configuration processing
		this.axiosInstance.interceptors.request.use((config: any) => {
			const { ignoreCancelToken } = config.requestOptions
			const ignoreCancel =
				ignoreCancelToken !== undefined ? ignoreCancelToken : this.options.requestOptions?.ignoreCancelToken

			!ignoreCancel && axiosCanceler.addPending(config)
			if (requestInterceptors && isFunction(requestInterceptors)) {
				config = requestInterceptors(config, this.options)
			}
			return config
		}, undefined)

		// Request interceptor error capture
		requestInterceptorsCatch &&
			isFunction(requestInterceptorsCatch) &&
			this.axiosInstance.interceptors.request.use(undefined, requestInterceptorsCatch)

		// Response result interceptor processing
		this.axiosInstance.interceptors.response.use((res: AxiosResponse<any>) => {
			res && axiosCanceler.removePending(res.config)
			if (responseInterceptors && isFunction(responseInterceptors)) {
				res = responseInterceptors(res)
			}
			return res
		}, undefined)

		// Response result interceptor error capture
		responseInterceptorsCatch &&
			isFunction(responseInterceptorsCatch) &&
			this.axiosInstance.interceptors.response.use(undefined, responseInterceptorsCatch)
	}

	// support form-data
	supportFormData(config: AxiosRequestConfig) {
		const headers = config.headers || this.options.headers
		const contentType = headers?.['Content-Type'] || headers?.['content-type']

		if (
			contentType !== ContentTypeEnum.FORM_URLENCODED ||
			!Reflect.has(config, 'data') ||
			config.method?.toUpperCase() === RequestEnum.GET
		) {
			return config
		}

		return {
			...config,
			data: qs.stringify(config.data, { arrayFormat: 'brackets' })
		}
	}

	get<T = any>(config: AxiosRequestConfig, options?: RequestOptions): Promise<T> {
		return this.request({ ...config, method: 'GET' }, options)
	}

	post<T = any>(config: AxiosRequestConfig, options?: RequestOptions): Promise<T> {
		return this.request({ ...config, method: 'POST' }, options)
	}

	put<T = any>(config: AxiosRequestConfig, options?: RequestOptions): Promise<T> {
		return this.request({ ...config, method: 'PUT' }, options)
	}

	delete<T = any>(config: AxiosRequestConfig, options?: RequestOptions): Promise<T> {
		return this.request({ ...config, method: 'DELETE' }, options)
	}

	request<T = any>(config: AxiosRequestConfig, options?: RequestOptions): Promise<T> {
		let conf: CreateAxiosOptions = cloneDeep(config)
		const transform = this.getTransform()

		const { requestOptions } = this.options

		const opt: RequestOptions = Object.assign({}, requestOptions, options)

		const { beforeRequestHook, requestCatchHook, transformRequestHook } = transform || {}

		if (beforeRequestHook && isFunction(beforeRequestHook)) {
			conf = beforeRequestHook(conf, opt)
		}
		conf.requestOptions = opt

		conf = this.supportFormData(conf)

		return new Promise((resolve, reject) => {
			this.axiosInstance
				.request<any, AxiosResponse<RequestResult>>(conf)
				.then((res: AxiosResponse<RequestResult>) => {
					if (transformRequestHook && isFunction(transformRequestHook)) {
						try {
							const ret = transformRequestHook(res, opt)
							resolve(ret)
						} catch (err) {
							reject(err || new Error('request error!'))
						}
						return
					}
					resolve(res as unknown as Promise<T>)
				})
				.catch((e: Error | AxiosError) => {
					if (requestCatchHook && isFunction(requestCatchHook)) {
						reject(requestCatchHook(e, opt))
						return
					}
					if (axios.isAxiosError(e)) {
						// rewrite error message from axios in here
					}
					reject(e)
				})
		})
	}
}
